﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Xml.Linq;
using ThreeByte.ActiveDeck.Config;
using ThreeByte.ActiveDeck.Content;
using ThreeByte.ActiveDeck.Data;
using ThreeByte.ActiveDeck.Service;
using ThreeByte.TaskModel;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using log4net;
using System.IO;

namespace ThreeByte.ActiveDeck.AddIn.Config
{
    public class UploadPresentationTask : AsyncTaskBase
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(UploadPresentationTask));

        private CoreConfig Config;
        private Dispatcher Dispatcher;

        private IAsyncTask _subTask;
        public IAsyncTask SubTask {
            get {
                return _subTask;
            }
            set {
                _subTask = value;
                NotifyPropertyChanged("SubTask");
            }
        }

        private UserControl _currentPreview;
        public UserControl CurrentPreview {
            get {
                return _currentPreview;
            }
            set {
                _currentPreview = value;
                NotifyPropertyChanged("CurrentPreview");
            }
        }

        public UploadPresentationTask(CoreConfig config, Dispatcher dispatcher) {
            Config = config;
            Dispatcher = dispatcher;
            Name = string.Format("Publishing: {0} ({1})", Config.Name, Config.ID);
        }

        public override bool Setup() {
            List<int> slideIds = SlideUtil.GetSlideIdsInPresentationOrder(Config.Presentation);
            //Set up slides in presentation order
            foreach(int id in slideIds) {
                if(Config.SlideIDs.Contains(id)) {
                    SlideIdsToUpload.Add(id);
                }
            }

            return base.Setup();
        }

        private List<int> SlideIdsToUpload = new List<int>();

        protected override void Run(BackgroundWorker bw, DoWorkEventArgs e) {

            if (bw.CancellationPending) {
                e.Cancel = true;
                return;
            }
            
            //Prepare the presentation config
            PresentationConfiguration presConfig = new PresentationConfiguration(Config);
            WebServiceClient client = new WebServiceClient(Config.PresenterLogin);
            HashSet<Guid> uploadedResources = new HashSet<Guid>();

            //TODO: Consolidate Resources
            RetrieveResourcesTask retrieveTask = new RetrieveResourcesTask(Config.ID, client);
            retrieveTask.Go();
            SubTask = retrieveTask;
            retrieveTask.Join(bw);

            HashSet<Guid> allResourceIds = retrieveTask.AllResources;
            HashSet<Guid> unusedResources = new HashSet<Guid>();
            foreach(Guid g in allResourceIds) {
                unusedResources.Add(g);
            }
            log.DebugFormat("GraphicResources cached in cloud: {0}", allResourceIds.Count);

            //Get the current slide count
            int totalSlideCount = Config.SlideIDs.Count();

            int currentSlideNum = 0;
            foreach (int slideId in SlideIdsToUpload) {
                log.InfoFormat("Upload slide ID {0}", slideId);
                if (bw.CancellationPending) {
                    e.Cancel = true;
                    return;
                }
                bw.ReportProgress((int)((currentSlideNum++ * 100.0) / (double)totalSlideCount));

                foreach(PageConfig page in Config[slideId].Pages) {
                    //PageConfig currentSlideConfig = Config[slideId];

                    //Construct the preview on the Main Thread
                    System.Threading.Thread.Sleep(1);  //Does this help?
                    Dispatcher.Invoke(new Action<PageConfig, PresentationConfiguration>(PreviewSlide), page, presConfig);

                    //When it returns, upload the new content
                    //Upload all resources which haven't been uploaded yet

                    //PARALLEL ---
                    //Upload all in parallel, this significantly cuts down on upload time because each roundtrip to verify a cached
                    //asset can be done simultaneously
                    System.Threading.Tasks.Parallel.ForEach(presConfig.Resources.Keys.OrderBy(i => i), (g, loopstate) =>
                    {
                        if(bw.CancellationPending) {
                            e.Cancel = true;
                            loopstate.Stop(); //Must Break //return;
                        }
                        if(uploadedResources.Contains(g)) {
                            return;
                        }

                        Status = string.Format("Publishing slide {0}...", currentSlideNum);
                        lock(allResourceIds) {
                            if(allResourceIds.Contains(g)) {
                                unusedResources.Remove(g);
                                return;  //Don't need to upload it
                            }
                        }
                        UploadResourceTask newResourceTask = new UploadResourceTask(Config.ID, presConfig.Resources[g], client);
                        newResourceTask.Go();
                        SubTask = newResourceTask;
                        newResourceTask.Join(bw);
                        if(bw.CancellationPending) {
                            e.Cancel = true;
                            loopstate.Stop(); //Must Break //return;
                        }
                        if(newResourceTask.HasError) {
                            HasError = true;
                            Error = "Resource could not be uploaded:\n" + newResourceTask.Error;
                            loopstate.Stop(); //Must Break //return;
                        }

                        uploadedResources.Add(g);
                    });
                    //END PARALLEL

                    //Upload all MediaResources which haven't been uploaded yet
                    foreach(Guid g in presConfig.MediaResources.Keys.OrderBy(i => i)) {
                        if(bw.CancellationPending) {
                            e.Cancel = true;
                            return;
                        }
                        if(uploadedResources.Contains(g)) {
                            continue;
                        }

                        Status = string.Format("Publishing slide {0}...", currentSlideNum);
                        lock(allResourceIds) {
                            if(allResourceIds.Contains(g)) {
                                unusedResources.Remove(g);
                                continue;  //Don't need to upload it
                            }
                        }
                        UploadResourceTask newResourceTask = new UploadResourceTask(Config.ID, presConfig.MediaResources[g], client);
                        newResourceTask.Go();
                        SubTask = newResourceTask;
                        newResourceTask.Join(bw);
                        if(bw.CancellationPending) {
                            e.Cancel = true;
                            return;
                        }
                        if(newResourceTask.HasError) {
                            HasError = true;
                            Error = "Resource could not be uploaded:\n" + newResourceTask.Error;
                            return;
                        }

                        uploadedResources.Add(g);
                    }

                }
                
            }

            if (bw.CancellationPending) {
                e.Cancel = true;
                return;
            }
            //Upload the presentation config next
            XElement configXml = presConfig.ToXml();
            XElement pres = client.PublishPresentationCompressed(Config.ID, configXml);
            PresentationHeader presHeader = new PresentationHeader(pres);
            bw.ReportProgress(100);
            
            log.DebugFormat("GraphicResources cached in cloud: {0}", allResourceIds.Count);
            log.DebugFormat("Unused GraphicResources remaining in cloud: {0}", unusedResources.Count);
            //Consider purging all unused resources:
            Status = string.Format("Cleaning up unused resources...");

            DeleteResourcesTask deleteTask = new DeleteResourcesTask(Config.ID, client, unusedResources.ToList());
            deleteTask.Go();
            SubTask = deleteTask;
            deleteTask.Join(bw);

            Status = string.Format("Publish Complete!  Version: {0}", presHeader.Version);
        }


        private void PreviewSlide(PageConfig pageConfig, PresentationConfiguration presConfig) {            

            Status = @"Previewing Slide...";
            pageConfig.Refresh();
            CurrentPreview = PagePreviewFactory.Create(pageConfig);
           
            //Generate Page Config
            if (CurrentPreview is IPreviewControl) {
                PresentationPageConfiguration presentationPageConfig = ((IPreviewControl)CurrentPreview).GetPageConfiguration();
                //Add to presentation Config
                //TODO: Do this and return from the function?
                presConfig.AddPageConfig(pageConfig.ParentSlideConfig.SlideId, presentationPageConfig);
            }
        }
       

    }
}
